#!/usr/bin/perl
#
# Command line package manager for Mac OS X
# Copyright (c) 2008 Rudá Moura. All Rights Reserved.
#

use warnings;
use strict;
use Foundation;
use Scalar::Util qw(blessed);
use Getopt::Std;
use File::Basename;

# Packages' meta-information are stored here
my $Receipts = "/Library/Receipts";

# List content in BOM archive
my $Lsbom = "lsbom -pf -f -l";

sub version {
	print "Package Manager for Mac OS X\n";
	print "Copyright (c) 2008 Rudá Moura\n";
	exit 0;
}

sub usage {
	my $progname = basename $0;
	print <<EOF;
Usage: $progname [OPTION] [NAME]
Print all packages installed unless options are given:
  -h	This help message
  -v	Print version
  -l	List all packages with name, version and vendor
  -I	Show package information (name, version and vendor)
  -L	List package content
  -S	Search for pattern in all packages
  -i	Install package
  -r	Remove package
Note: the suffix .pkg is optional
EOF
	exit 0;
}

sub get_all_packages {
	opendir PKGS, $Receipts;
	my @pkgs = grep { /\.pkg$/ } readdir PKGS;
	closedir PKGS;
	# Filter by directories because BSD.pkg in Leopard is a file
	@pkgs = grep { -d "$Receipts/$_" } @pkgs;
	return @pkgs;
}

sub get_package_info {
	my $pkg = shift;
	my %info = ();
	my $info_plist = "$Receipts/$pkg/Contents/Info.plist";
	my $info_dic = NSDictionary->dictionaryWithContentsOfFile_($info_plist);
	my $id = $info_dic->objectForKey_("CFBundleIdentifier");
	$id = blessed $id ? $id->UTF8String() : "none";
	$info{"id"} = $id;
	my $name = $info_dic->objectForKey_("CFBundleName");
	$name = blessed $name ? $name->UTF8String() : "none";
	$info{"name"} = $name;
	my $version = $info_dic->objectForKey_("CFBundleShortVersionString");
	$version = blessed $version ? $version->UTF8String() : "none";
	$info{"version"} = $version;
	
	return %info;
}

sub get_package_content {
	my $pkg = shift;
	my $archive = "$Receipts/$pkg/Contents/Archive.Bom";
	my @files = ();
	if (-f $archive) {
		open(CONTENT, "$Lsbom \"$archive\"|");
		while (<CONTENT>) {
			chomp;
			s/^\.//; # ./usr/local/bin/... -> /usr/local/bin/...
			push @files, $_;
		}
		close CONTENT;
		return @files;
	}
	warn("Could not find BOM file '$archive'");
}

sub show_package_info {
	my $pkg = shift;
	my %info = get_package_info $pkg;
	print "$info{name} version $info{version} ($info{id})\n";
}

sub show_package_content {
	my $pkg = shift;
	my @content = get_package_content $pkg;
	foreach my $file (@content) {
		print "$file\n";
	}
}

sub show_all_packages {
	my @pkgs = get_all_packages;
	foreach my $pkg (@pkgs) {
		print "$pkg\n";
	}
}

sub show_all_packages_long {
	my @pkgs = get_all_packages;
	foreach	my $pkg (@pkgs) {
		show_package_info $pkg;
	}
}

sub match_pattern_in_package {
	my ($pattern, $pkg) = @_;
	my @content = get_package_content $pkg;
	# The code bellow trigger a segmentation fault in perl:
	#my @matched = ();
	#while (<@content>) {
	#	#print "$_\n";
	#	push @matched, $_ if grep $pattern;
	#}
	$pattern =~ s!/!\/!g;
	my @matched = grep (/$pattern/, @content);
	return @matched;
}

sub search_pattern_in_package {
	my ($pattern, $pkg) = @_;
	my @matched = match_pattern_in_package $pattern, $pkg;
	foreach my $line (@matched) {
		print "$pkg: $line\n";
	}
}

sub search_pattern_in_all_packages {
	my $pattern = shift;
	my @pkgs = get_all_packages;
	foreach my $pkg (@pkgs) {
		#print "Searching in " . $pkg . "\n";
		search_pattern_in_package $pattern, $pkg;
	}
}

sub install_package {
	my $pkg = shift;
	my $status = system "installer -pkg \"$pkg\" -target /";
}

sub remove_package {
	$< == 0 or die "This package requires authentication to remove.\n";
	my $pkg = shift;
	my @content = get_package_content $pkg;
	my $cnt = 0;
	foreach my $file (@content) {
		#print $file . "\n";
		#$file =~ s/ /\\ /g;
		#$file =~ s/\(/\\(/g;
		#$file =~ s/\)/\\)/g;
		$cnt += unlink $file;
	}
	print "Removed $cnt file(s)\n";
	$pkg =~ s/ /\\ /g;
	system "rm -rf \"$Receipts/$pkg\"";
}

sub append_suffix {
	my $pkg = shift;
	return $pkg =~ /\.pkg$/ ? $pkg : "$pkg.pkg";
}

if ($#ARGV == -1) {
	show_all_packages;
	exit 0;
}
my %options;
getopts ("li:r:I:S:L:hv", \%options);
usage if not %options or $options{"h"};
version if $options{"v"};
show_all_packages_long if ($options{"l"});
show_package_info append_suffix $options{"I"} if $options{"I"};
show_package_content append_suffix $options{"L"} if $options{"L"};
search_pattern_in_all_packages $options{"S"} if $options{"S"};
install_package append_suffix $options{"i"} if $options{"i"};
remove_package append_suffix $options{"r"} if $options{"r"};
